package com.svconnect.plugin.builders;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import com.svconnect.plugin.model.FileContent;
import com.svconnect.plugin.model.wsdl.ComplexDataType;
import com.svconnect.plugin.model.wsdl.EComplexType;
import com.svconnect.plugin.model.wsdl.IDataType;
import com.svconnect.plugin.model.wsdl.Operation;
import com.svconnect.plugin.model.wsdl.Parameter;
import com.svconnect.plugin.model.wsdl.Service;
import com.svconnect.plugin.model.wsdl.SimpleDataType;
import com.svconnect.plugin.util.Log;
import com.svconnect.plugin.util.StringUtil;

public class DataContractBuilder extends BuilderBase {

	private String packageName;
	
	private List<ComplexDataType> resolvedClasses;
	private List<FileContent> builtClasses;
	
	public DataContractBuilder(String packageName, String referenceDir) {
		this.setPackageName(packageName);
		this.setReferenceDir(referenceDir);
	}
	
	public String getPackageName() {
		return packageName;
	}

	public void setPackageName(String packageName) {
		this.packageName = packageName;
	}	
	
	public boolean build(List<Service> services) {
		Log.i("Building data contract classes.");
		List<FileContent> clss = this.buildClasses(services);
		if (clss!=null) {
			try {
				this.createPackageDir(services,this.getPackageName());
				this.save(clss);
				return true;
			} catch (IOException e) {
				Log.e(e);
			}
		}
		return false;
	}
	
	private void save(List<FileContent> classes) throws IOException {
		if (classes!=null) {
			for(FileContent cls : classes) {
				String filename = String.format("%s.java",cls.getClassName());
				Log.i("Creating "+filename);
				String filepath = this.getPackageDir(cls.getService().getName(), packageName)+"/"+filename;
				FileWriter fw = new FileWriter(filepath);
				fw.write(cls.getClassContent());
				fw.setReadOnly();
			}
		}
	}
	
	private List<FileContent> buildClasses(List<Service> services) {
		if (services!=null) {
			this.resolvedClasses = new ArrayList<ComplexDataType>();
			this.builtClasses = new ArrayList<FileContent>();			
			for(Service svc : services) {
				List<Operation> operations = svc.getOperations();
				for(Operation op : operations) {
					List<Parameter> params = op.getParameters();
					for(Parameter p : params) {
						this.resolveType(svc,p.getType());
					}
					IDataType responseType = op.getResponse();
					this.resolveType(svc,responseType);
					
					List<ComplexDataType> faults = op.getFaults();
					for(ComplexDataType f : faults) {
						this.resolveType(svc,f);
					}
				}
			}
			return builtClasses;
		}
		return null;
	}
	
	private void resolveType(Service svc, IDataType type) {
		if (type instanceof ComplexDataType) {
			ComplexDataType cdt = (ComplexDataType)type;
			
			ComplexDataType baseType = cdt.getBaseType();
			if (baseType!=null)
				this.resolveType(svc, baseType);
			
			switch(cdt.getType()) {
				case DICTIONARY:
					this.resolveDictionaryType(svc,cdt);
					break;
				case SEQUENCE:
					this.resolveListType(svc,cdt);
					break;
				case ENUMERATION:
					this.resolveEnumerationType(svc,cdt);
				default:
					this.resolveCustomType(svc,cdt);
					break;
			}
		}
	}
	
	private void resolveDictionaryType(Service svc, ComplexDataType cdt) {
		IDataType keyType = null;
		IDataType valType = null;
		ComplexDataType keyValPair = (ComplexDataType)cdt.getMembers().get(0).getType();
		for(Parameter p : keyValPair.getMembers()) {
			if (p.getName().equals("Key"))
				keyType = p.getType();
			else if (p.getName().equals("Value"))
				valType = p.getType();
		}
		
		this.resolveType(svc,keyType);
		this.resolveType(svc,valType);		
	}

	private void resolveListType(Service svc, ComplexDataType cdt) {
		this.resolveType(svc,cdt.getMembers().get(0).getType());		
	}	
	
	private void resolveEnumerationType(Service svc, ComplexDataType cdt) {
		if (!isAlreadyResolved(cdt)) {
			
			resolvedClasses.add(cdt);
			
			
			FileContent cls = new FileContent();
			cls.setService(svc);
			cls.setClassName(cdt.getName());
			StringBuilder content = new StringBuilder();
			content.append(String.format("package %s;\n\n", this.packageName));
			
			if (cdt.getNamespace()!=null) {
				content.append(String.format("import %s.annotations.DataContract;\n\n", BuilderBase.LIBRARY_PACKAGE));
				content.append(String.format("@DataContract(namespace=\"%s\")\n",cdt.getNamespace()));
			}
			
			content.append(String.format("public enum %s { \n\n", cdt.getName()));
			
			List<Parameter> params = cdt.getMembers();
			int count = params.size();
			for(int i=0; i<count; i++) {
				Parameter p = params.get(i);
				content.append(String.format("\t%s", p.getName()));
				if (i!=count-1)
					content.append(",");
				content.append("\n");
			}
			
			content.append("}\n");
			cls.setClassContent(content.toString());
			this.builtClasses.add(cls);
		}
	}
	
	private void resolveCustomType(Service svc, ComplexDataType cdt) {
		if (!isAlreadyResolved(cdt)) {
			resolvedClasses.add(cdt);
			
			FileContent cls = new FileContent();
			cls.setClassName(cdt.getName());
			cls.setService(svc);
			StringBuilder content = new StringBuilder();
			content.append(String.format("package %s;\n\n", this.packageName));
			content.append(String.format("import %s.annotations.*;\n\n", BuilderBase.LIBRARY_PACKAGE));
			content.append(String.format("@DataContract(namespace=\"%s\")\n",cdt.getNamespace()));
			content.append(String.format("public class %s", cdt.getName()));
			
			ComplexDataType baseType = cdt.getBaseType();
			if (baseType!=null)
				content.append(String.format(" extends %s",baseType.getName()));
			else if (cdt.getType() == EComplexType.EXCEPTION)
				content.append(String.format(" extends Exception implements %s.exceptions.IException", BuilderBase.LIBRARY_PACKAGE));

			content.append(" { \n\n");
			
			if (cdt.getType() == EComplexType.EXCEPTION)
				content.append("\tprivate static final long serialVersionUID = 1L;\n\n");
			
			List<Parameter> params = cdt.getMembers();					
			if (isJavaConventionEnabled()) {
				for(Parameter p : params) {
					content.append(String.format("\t@DataMember(name=\"%s\"",p.getName()));
					if (p.getType() instanceof ComplexDataType) {
						ComplexDataType pType = (ComplexDataType)p.getType();
						if (!cdt.getNamespace().equals(pType.getNamespace()) && pType.getNamespace()!=null) {
							content.append(String.format(", namespace=\"%s\"", pType.getNamespace()));
						}
						if (cdt.getType()==EComplexType.ENUMERATION && !cdt.isNullable())
							content.append(", nullable=false");
						content.append(")\n");
						if (pType.getType()==EComplexType.DICTIONARY)
							content.append(String.format("\t@Dictionary(\"%s\")\n",pType.getMembers().get(0).getType()));
						
					}
					else if (p.getType() instanceof SimpleDataType){
						SimpleDataType pType = (SimpleDataType)p.getType();
						if (!pType.isNullable())
							content.append(", nullable=false");
						content.append(")\n");
					}
					
					content.append(String.format("\tprivate %s %s;\n\n", p.getType().toString(), StringUtil.toLowerCaseFirst(p.getName())));
					
				}
				for(Parameter p : params) {
					// getter
					String lcName = StringUtil.toLowerCaseFirst(p.getName());
					String ucfName = StringUtil.toUpperCaseFirst(p.getName());
					
					content.append(String.format("\tpublic %s get%s() {\n", p.getType().toString(), ucfName));
					content.append(String.format("\t\treturn this.%s;\n\t}\n\n", lcName));
					// setter
					content.append(String.format("\tpublic void set%s(%s %s) {\n", ucfName, p.getType().toString(), lcName));
					content.append(String.format("\t\tthis.%s = %s;\n\t}\n\n", lcName, lcName));
				}
			}
			else {
				for(Parameter p : params) {
					content.append(String.format("\t@DataMember(name=\"%s\"",p.getName()));
					if (p.getType() instanceof ComplexDataType) {
						ComplexDataType pType = (ComplexDataType)p.getType();
						if (!cdt.getNamespace().equals(pType.getNamespace()) && pType.getNamespace()!=null) {
							content.append(String.format(", namespace=\"%s\"", pType.getNamespace()));
						}
					}
					content.append(")\n");
					content.append(String.format("\tpublic %s %s;\n\n", p.getType().toString(), p.getName()));
				}
			}
			content.append("}\n");
			cls.setClassContent(content.toString());
			this.builtClasses.add(cls);
			
			for(Parameter p : params) {
				this.resolveType(svc,p.getType());
			}
		}		
	}
	
	
	private boolean isAlreadyResolved(ComplexDataType cdt) {
		if (resolvedClasses!=null) {
			for(ComplexDataType type : resolvedClasses) {
				if (type.getName().equals(cdt.getName()) &&
					type.getNamespace().equals(cdt.getNamespace())) {
					return true;
				}
			}
		}
		return false;
	}
	
	
}
